/* Copyright 2010 Hugh Greenberg */   

package com.hugegreenbug.msoss;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Random;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import android.R.drawable;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Process;
import android.os.SystemClock;
import android.provider.MediaStore;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

import com.hugegreenbug.msoss.R;
import com.hugegreenbug.msoss.Cube;
import com.rbgrn.net.GLWallpaperService;

public class Ms extends GLWallpaperService {
 	
    @Override
    public void onCreate() {
        super.onCreate();
    }
    
    @Override
    public void onDestroy() {
    	super.onDestroy();
    }
    
    @Override
    public Engine onCreateEngine() {
    	return new msEngine();
    }
    
    class msEngine extends GLEngine implements SharedPreferences.OnSharedPreferenceChangeListener {
    	public static final String SHARED_PREFS_NAME="mssettings";
    	public static final String WIKI_COMMONS_BASE="http://commons.wikimedia.org";
    	private static final long ERROR_RECOVER = 5000;  //Milliseconds to restart download for a recoverable failure
    	private static final long ERROR_STD = 300000;    //Milliseconds to restart download for a serious error
    	private static final int ERROR_MAX_RETRIES = 20; //Maximum number of retries before stopping downloads for a while
     	private static final int GET_IMAGE = 1;
     	private static final String CUSTOM_BG_FILE="background.png";
    	private DefaultHttpClient client;
    	private SharedPreferences mPrefs;
    	private volatile boolean mVisible;
    	private volatile WikiCommonsImage curWikiImage;
    	private ArrayList<String> mNotFound = new ArrayList<String>();
    	private int MAX_NOT_FOUND = 50;
    	private volatile long mScheduledUpdate;
    	private final float mThresh = 0.2f;
    	private int mRetries;
    	private volatile String mSelection;
    	private volatile boolean mFull;
    	private volatile int mRefreshRate;
    	private volatile boolean mSettingWikiImage;
    	private volatile boolean mGettingWikiImage;
    	private volatile boolean mDestroyed;
    	private volatile boolean mSettingPreferences;
    	private volatile boolean mImageViewed;
      	private volatile boolean mSkipArtist;
      	private int mNumTaps;
 	    private long mLastTouch;
 	    private float mFirstTouchX;
 	    private float mFirstTouchY;
 	    private volatile MsRenderer mRenderer;
 	    private Cube mCube;
 	    private Wall mWall;
 	    private volatile boolean mFirstImage;
 	    private volatile boolean mRefreshGesture;
 	    private volatile Background mBackground;
 	    private volatile boolean mSettingBg;
 	    private volatile String mCubeBg;
 	    private volatile String mCubeCustomBg;
 	    private NotificationManager mNotifyMan;
    	private ImageScheduler mImageScheduler;
    	private final Runnable mGetImage = new Runnable() {
    		public void run() {
    			getImage();
    		}
    	};
    	
    	msEngine() {
    		super();
    		client = null;    	
          	mRenderer = new MsRenderer();
          	mCube = new Cube();
          	mWall = new Wall();
          	mBackground = new Background();
          	mRenderer.setCube(mCube);
          	mRenderer.setWall(mWall);
          	mRenderer.setBackground(mBackground);
    		mPrefs = getSharedPreferences(SHARED_PREFS_NAME, 0);
    		mPrefs.registerOnSharedPreferenceChangeListener(this);
            //onSharedPreferenceChanged(mPrefs, null);
            SharedPreferences.Editor editor = mPrefs.edit();
            mSelection = mPrefs.getString("ms_image_select", "best");
            mFull = mPrefs.getBoolean("ms_image_full", false);            
            try {
            	String rate = mPrefs.getString("ms_refresh_rate", "3");
            	mRefreshRate = Integer.parseInt(rate) * 60;
            	if (mRefreshRate < 60 || mRefreshRate > 1440) {
            		mRefreshRate = 60;
            		editor.putString("ms_refresh_rate", "1");
            	}
            } catch (NumberFormatException nfe) {
            	mRefreshRate = 60;
            	editor.putString("ms_refresh_rate", "1");
            }
            
            mNotifyMan = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            mRenderer.setCubeScale(mPrefs.getBoolean("ms_cube_scale", true));
            mRenderer.setDisplayArtistImage(mPrefs.getBoolean("ms_display_artist", true));
            mRenderer.setDisplayArtistVisible(mPrefs.getBoolean("ms_display_artist_visible", false));
            mSkipArtist = mPrefs.getBoolean("ms_artist_skip", true);
            setImageDisplay(mPrefs.getString("ms_image_display", "cube"));
            mCubeBg = mPrefs.getString("ms_cube_bg", "default_bg");
            mCubeCustomBg = mPrefs.getString("ms_custom_background_uri", "unknown");
            mRenderer.setStretchBg(mPrefs.getBoolean("ms_cubebg_stretch", true));
            mRenderer.setStretchWall(mPrefs.getBoolean("ms_wall_stretch", true));
            try {
            	String cc = (mPrefs.getString("ms_cube_color", "0"));
            	int color = Integer.parseInt(cc);
            	mRenderer.setClearColor(color);
            } catch (NumberFormatException nfe) {
            	mRenderer.setClearColor(0);
            	editor.putString("ms_cube_color", "0");
            }
            
            int textColor = -1;
            try {
            	String color = mPrefs.getString("ms_text_color", "-1");
            	textColor = Integer.parseInt(color);
            } catch (NumberFormatException nfe) {
            	textColor = -1;
            	editor.putString("ms_image_display", "-1");
            }
            
            mRenderer.setTextColor(textColor);
            float newTextSize = 40.0f;
            try {
            	String textSize = mPrefs.getString("ms_artist_text_size", "40.0");
            	newTextSize = Float.parseFloat(textSize);
            	if (newTextSize < 20.0f || newTextSize > 100.0f)
            		newTextSize = 40.0f;
            } catch (NumberFormatException nfe) {
            	newTextSize = 40.0f;
            	editor.putString("ms_artist_text_size", "40.0");
            }
            
            mVisible = true;
            mRenderer.setTextSize(newTextSize);
        	mFirstImage = true;
        	mRefreshGesture = false;
        	curWikiImage = null;
        	mScheduledUpdate = System.currentTimeMillis() + mRefreshRate * 60 * 1000;
          	mSettingWikiImage = false;
          	mGettingWikiImage = false;
          	mSettingPreferences = false;
          	mDestroyed = false;
          	mImageViewed = false;
          	mNumTaps = 0;
          	mLastTouch = 0;
          	mSettingBg = false;
          	mRetries = 0;
          
          	editor.commit();
          	setRenderer(mRenderer);
          	setRenderMode(RENDERMODE_CONTINUOUSLY);
          	mImageScheduler = new ImageScheduler(mGetImage);
            updateImageHandlerMills(200); //Start the first image download
    	}
    	
    	public void setImageDisplay(String imageDisplay) {
    		 if (imageDisplay.equals("cube"))
             	mRenderer.setImageDisplay(MsRenderer.CUBE_MODE);
             else if (imageDisplay.equals("wall"))
             	mRenderer.setImageDisplay(MsRenderer.WALL_MODE);
             else 
             	mRenderer.setImageDisplay(MsRenderer.ZOOM_MODE);
             
    	}
    	
    	public void updateStorageState(boolean state) {
    		if (state) {
  				updateImageHandlerDiff();
    		} 
    	}
    	
    	public void setBackground(boolean custBgChange) {		
    		while (mSettingWikiImage) {
    			SystemClock.sleep(100);
    		}
    		
    		mSettingBg = true;
    		mBackground.setByteBuffer(null);
    		Bitmap bmap = null;
    		//int maxTexSize = mRenderer.getMaxTexSize();
    		int canvasWidth = mRenderer.getCanvasWidth();
			int canvasHeight = mRenderer.getCanvasHeight();
			int origWidth, origHeight;
    		try {
    			BitmapFactory.Options opt = new BitmapFactory.Options();
    			opt.inJustDecodeBounds = true;
    			
    			float wr = 1.0f;
    			float hr = 1.0f;	
    			System.gc();
       			if (mCubeBg.equals("default_bg")) {
       				BitmapFactory.decodeResource(getResources(), R.drawable.default_bg, opt);
       				origWidth = opt.outWidth;
       				origHeight = opt.outHeight;
       				wr = (float) origWidth/canvasWidth;
       				hr = (float) origHeight/canvasHeight;
       				opt.inJustDecodeBounds = false;
       				opt.inSampleSize = (int) Math.ceil(Math.min(wr, hr));
    				bmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_bg, opt);
       		    } else if (mCubeBg.equals("custom_background") && !mCubeCustomBg.equals("unknown")) {
       		    	if (custBgChange) {
       		    		File newBg = new File(mCubeCustomBg);
       		    		if (newBg.exists())
       		    			BitmapFactory.decodeFile(mCubeCustomBg, opt);
       		    		else
       		    			opt = null;
       		    	} else {
       		    		try {
       		    			FileInputStream fis = openFileInput(CUSTOM_BG_FILE);
           		    		BitmapFactory.decodeStream(fis, null, opt);
       		    			fis.close();
       		    		} catch (FileNotFoundException fne) {
       		    			File newBg = new File(mCubeCustomBg);
           		    		if (newBg.exists()) {
           		    			BitmapFactory.decodeFile(mCubeCustomBg, opt);
           		    			custBgChange = true;
           		    		} else
           		    			opt = null;
       		    		} catch (IOException e) {
							opt = null;
						}
       		    	}

       		    	if (opt != null) {
       		    		origWidth = opt.outWidth;
       		    		origHeight = opt.outHeight;
       		    		opt.inJustDecodeBounds = false;
       		    		if (custBgChange) {
       		    			wr = (float) origWidth/canvasWidth;
           		    		hr = (float) origHeight/canvasHeight;
           		    		opt.inSampleSize = (int) Math.ceil(Math.min(wr, hr));
       		    			bmap = BitmapFactory.decodeFile(mCubeCustomBg, opt);
       		    			FileOutputStream fos;
							try {
								fos = openFileOutput(CUSTOM_BG_FILE, Context.MODE_PRIVATE);
								if (bmap != null)
									bmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
	       		    			fos.close();
							} catch (FileNotFoundException e) {
								Log.e("Musician Slideshow", "File not found error while writing custom background to app dir");
							} catch (IOException e) {
								Log.e("Musician Slideshow", "I/O error while writing custom background to app dir");
							}
       		    		} else {
       		    			try {
       		    				opt.inSampleSize = 0;
           		    			FileInputStream fis = openFileInput(CUSTOM_BG_FILE);
           		    			bmap = BitmapFactory.decodeStream(fis, null, opt);
           		    			fis.close();
           		    		} catch (FileNotFoundException fne) {
								Log.e("Musician Slideshow", "File not found error while writing custom background to app dir");
           		    		} catch (IOException e) {
								Log.e("Musician Slideshow", "I/O error while writing custom background to app dir");
    						}
       		    		}
       		    	}
       		    } else {
       		     	mSettingBg = false;
       				return;
       			}
    		} catch (OutOfMemoryError ome) {
    			Log.e("Musician Slideshow", "Out of memory error while creating bmap for bg: " 
    					+ mCubeBg + " error: " + ome.getMessage());
    	     	mSettingBg = false;
	        	return;
	        }
    		
    		if (bmap != null) 
    			scaleBmap(bmap, mBackground);
    			
	     	System.gc();
	     	mRenderer.setClearColor();
	     	mSettingBg = false;
    	}
    	
    	public void setPreviewImage() {
    		if (!isPreview())
    			return;
    		
            try {
           		mFirstImage = false;
           		WikiCommonsImage newWikiImage = new WikiCommonsImage();
           		BitmapFactory.Options opt = new BitmapFactory.Options();
           		opt.inJustDecodeBounds = true;
           		BitmapFactory.decodeResource(getResources(), 
     				R.drawable.preview_image, opt);
           		float wr = (float) opt.outWidth/mRenderer.getCanvasWidth(); 	
           		float hr = (float) opt.outHeight/mRenderer.getCanvasHeight(); 	
       			int ssample = (int) Math.ceil(Math.min(wr, hr));
       			opt.inJustDecodeBounds = false;
       			opt.inSampleSize = ssample;
       			System.gc();
       			Bitmap tempBmap = BitmapFactory.decodeResource(getResources(), 
        				R.drawable.preview_image, opt);
        		Background bg = scaleBmap(tempBmap, newWikiImage);
       			if (bg != null) {
       				newWikiImage.setText(new String[] {"Musician", "Slideshow"});
       				curWikiImage = newWikiImage;
       				newWikiImage = null;
       				mRenderer.setFadeIn(MsRenderer.MAX_FADEOUT);
       				mRenderer.setFadeOut(MsRenderer.MAX_FADEOUT);
       				mRenderer.setCurWikiImage(curWikiImage);
       			} 
       		} catch (OutOfMemoryError ome) {
           		Log.e("Musician Slideshow", "Out of memory while loading preview image");
           	}
            
    	}
    	
    	public synchronized void getImage() {
    		if (mDestroyed)
    			return;
    		
    		mGettingWikiImage = true;
    		mImageScheduler.cancelNextRun();
    		mRenderer.setError(MsRenderer.ERROR_NOERROR);
    		while ((mFirstImage && (mRenderer == null || mRenderer.getCanvasWidth() == 0 || 
    				mRenderer.getCanvasHeight() == 0 || 
    				mRenderer.getCanvasWidthThresh() == 0 || 
    				mRenderer.getCanvasHeightThresh() == 0)) || mSettingPreferences) {
				SystemClock.sleep(100);
			}
    		
    		if (isPreview()) {
    			setPreviewImage();
    			mGettingWikiImage = false;
    			return;
    		}
    		
    		if (mRenderer.getImageDisplay() == MsRenderer.CUBE_MODE && !mCubeBg.equals("none") && 
    				mBackground.getByteBuffer() == null && mFirstImage) {
    			mRenderer.settingBackground(true);
    			mRenderer.setRelease(MsRenderer.RELEASE_WALL_TEX);
               	setBackground(false);
    	     	mRenderer.setNewBg();
               	mRenderer.settingBackground(false);
            } else if (mRenderer.getImageDisplay() == MsRenderer.CUBE_MODE && mCubeBg.equals("none") && 
    					mFirstImage)		
            	mRenderer.setClearColor();
    		
    		Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
    		String curArtist = null;
    		if (client == null) {
    			client = new DefaultHttpClient();
    		}
    		
			ArrayList<WikiCommonsThumb> thumbs = null;
			float canvasRatio = mRenderer.getCanvasRatio();
			int canvasWidth = mRenderer.getCanvasWidth();
			int canvasHeight = mRenderer.getCanvasHeight();
			double canvasWidthThresh = mRenderer.getCanvasWidthThresh();
			double canvasHeightThresh = mRenderer.getCanvasHeightThresh();
            ImageFinderHandler imgFindHand = new ImageFinderHandler(canvasRatio);
            XMLReader xr = null;
            try {
				System.setProperty("org.xml.sax.driver","org.xmlpull.v1.sax2.Driver");
				xr = XMLReaderFactory.createXMLReader();
				xr.setContentHandler(imgFindHand);
	            xr.setErrorHandler(imgFindHand);
			} catch (SAXException e) {
		            handleGetImageError(false);
		            return;
			}
            	
	        while (curArtist == null) {
	        	ContentResolver res = getBaseContext().getContentResolver();
  				Cursor extCur = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, 
  						new String[] {"ARTIST"}, null, null, "RANDOM() LIMIT 1");
  				Cursor intCur = res.query(MediaStore.Audio.Media.getContentUri("phoneStorage"), 
  						new String[] {"ARTIST"}, null, null, "RANDOM() LIMIT 1");
  				
  				Cursor curCursor = null;
  				if (extCur != null && intCur == null) {
  					curCursor = extCur;
  				} else if (intCur != null && extCur == null) {
  					curCursor = intCur;
  				} else if (intCur != null && extCur != null) {
  					Random rand = new Random(System.currentTimeMillis());
  			        int r = rand.nextInt(2);
  			        if (r == 0) {
  			        	curCursor = intCur;
  			        	extCur.close();
  			        } else {
  			        	curCursor = extCur;
  			        	intCur.close();
  			        }
  				}
  				
  				if (curCursor != null) {
  					if (curCursor.moveToFirst())
  						curArtist = curCursor.getString(0);
  					
  					curCursor.close();
  				}
  		
  				res = null;
	       		if (curArtist == null) {
	       			if ((mFirstImage && mRetries == ERROR_MAX_RETRIES) || !mFirstImage)
	       				mRenderer.setError(MsRenderer.ERROR_NOARTIST);
	       			
	        		handleGetImageError(false);
	        		return;
	        	} 
	       		
	       		curArtist = curArtist.replace("&", "and");
	       		curArtist = curArtist.replace(' ', '_');
	        	if (mNotFound.contains(curArtist)) {
	        		if (mNotFound.size() >= MAX_NOT_FOUND)
	        			mNotFound.clear();
	        		
	        		handleGetImageError(true);
	        		return;
	       		} 
	        	  
	        	HttpResponse resp = null;
	        	try {
	        		HttpGet hget = new HttpGet(WIKI_COMMONS_BASE + "/wiki/Category:" + curArtist);
	       			resp = client.execute(hget);
	       		} catch (ClientProtocolException cpe) {
	       			handleGetImageError(false);
	        		return;
	        	} catch (IOException ioe) {
	       			handleGetImageError(false);
	        		return;
	        	} catch (IllegalArgumentException iae) {
	        		mNotFound.add(curArtist);
		        	handleGetImageError(true);
		        	return;
	        	}
	        	    
	        	HttpEntity ent = null;
	       		if (resp != null)
	       			ent = resp.getEntity();
	       		else {
	       			handleGetImageError(false);
	       			return;
	       		}
	        		
	       		if (resp.getStatusLine().equals("HTTP/1.0 404 Not Found")) {
	       			mNotFound.add(curArtist);
	       			handleGetImageError(true);
		        	return;
	       		}
	       		
	       		if (ent == null) {
	        		handleGetImageError(false);
	        		return;
	        	}
	        	
	        	try {
	        		xr.parse(new InputSource(ent.getContent()));
	        	} catch (IOException ioe) {
	        		handleGetImageError(false);	
	        		return;
	        	} catch (SAXException se) {
	        		handleGetImageError(false);	
	        		return;
	        	} 
	            
	        	if (!imgFindHand.isMusician()) {
	        		mNotFound.add(curArtist);
	        		handleGetImageError(true);
		        	return;
	        	}
	        	
	        	thumbs = imgFindHand.getThumbs();
	        	if (thumbs == null || thumbs.size() == 0) {
	        		mNotFound.add(curArtist);
	        		handleGetImageError(true);
		        	return;
	       		}
	       	}
	        
	        int maxIndex = 0;
	        if (imgFindHand.isSorted() && mSelection.equals("best") && mRenderer.getImageDisplay() != MsRenderer.CUBE_MODE) {
	        	for (int i = 0; i < thumbs.size(); i++) {
	        		WikiCommonsThumb thumb = thumbs.get(i);
	        		float diff = Math.abs(canvasRatio - thumb.getRatio());
	        		if (diff > 0 && diff <= mThresh && mSelection.equals("best")) {
	        			maxIndex = i;
	        		} else if (i == 0 && mSelection.equals("best") && mSkipArtist && 
	        				diff > mThresh) {
	        			mNotFound.add(curArtist);
	        			handleGetImageError(true);
	        			return;
	        		} else if (!mSelection.equals("best")){
	        			maxIndex = thumbs.size() - 1;
	        			break;
	        		} 
	        	}
	        	
	        } else if (mSelection.equals("best") && mRenderer.getImageDisplay() != MsRenderer.CUBE_MODE) {
	        	maxIndex = 0; 
	        } else {
	        	maxIndex = thumbs.size() - 1;
	        }
	        
	        ArrayList<WikiCommonsImage> pi = new ArrayList<WikiCommonsImage>();
	        for (int i = 0; i <= maxIndex; i++) {	        	
	        	WikiCommonsThumb thumb = thumbs.get(i);
	        	if (thumb == null) {
	        		return;
	        	}
	        
	        	String imageLink = thumb.getPageLink();
	        	HttpResponse resp = null;
	        	try {
	        		HttpGet hget = new HttpGet(imageLink);
	        		resp = client.execute(hget);
	        	} catch (ClientProtocolException cpe) {
	        		continue;
	        	} catch (IOException ioe) {
	        		continue;
	        	} catch (IllegalArgumentException iae) {
	        		continue;
	        	}
        	    	
	        	if (resp == null) {
	        		continue;
	        	}
        		
	        	HttpEntity ent = resp.getEntity();
	        	if (ent == null) {
	        		continue;
	        	}
        	
	        	ImageFileHandler imgFileHand = new ImageFileHandler(imageLink);
	        	try {
	        		xr.setContentHandler(imgFileHand);
	        		xr.setErrorHandler(imgFileHand);
	        		xr.parse(new InputSource(ent.getContent()));
	        	} catch (IOException ioe) {
	        		continue;
	        	} catch (SAXException se) {
	        		continue;
	        	} 
            
	        	WikiCommonsImage tempWikiImage = imgFileHand.getWikiCommonsImage();
	        	if (tempWikiImage == null || tempWikiImage.getFullLink() == null || 
	        			tempWikiImage.getThumbLink() == null) {
	        		continue;
	        	}
        		
	        	if ((tempWikiImage.getThumbWidth() >= canvasWidth - canvasWidthThresh 
	        		|| tempWikiImage.getThumbHeight() >= canvasHeight - canvasHeightThresh) && !mFull) {
	        		pi.add(tempWikiImage);
	        	} else if ((tempWikiImage.getFullWidth() >= canvasWidth - canvasWidthThresh ||
	        			tempWikiImage.getFullHeight() >= canvasHeight - canvasHeightThresh) || 
	        			(mFull && mRenderer.getImageDisplay() != MsRenderer.CUBE_MODE)) {
	        		pi.add(tempWikiImage);
	        	} else if (mSelection.equals("any")) {
	        		pi.add(tempWikiImage);
	        	}
	        }
	        
	        if (pi.size() == 0) {
	        	mNotFound.add(curArtist);
	        	handleGetImageError(true);
	        	return;
	        }
	        
	        Random rand = new Random(System.currentTimeMillis());
		    int r = rand.nextInt(pi.size());
		    WikiCommonsImage tempWikiImage = pi.get(r);
    		float wr = 1.0f;
    		float hr = 1.0f;
    		String link;
     
    		if (tempWikiImage.getThumbWidth() >= canvasWidth - canvasWidthThresh 
	        		|| tempWikiImage.getThumbHeight() >= canvasHeight - canvasHeightThresh) {
    			wr = tempWikiImage.getThumbWidth()/canvasWidth; 	
    			hr = tempWikiImage.getThumbHeight()/canvasHeight;
    			link = tempWikiImage.getThumbLink();
        	} else {       		
        		wr = tempWikiImage.getFullWidth()/canvasWidth; 	
        		hr = tempWikiImage.getFullHeight()/canvasHeight;
    			link = tempWikiImage.getFullLink();
        	} 
    		
    		int ssample = (int) Math.ceil(Math.min(wr,hr));
        	HttpResponse resp;
        	try {
       			HttpGet hget = new HttpGet(link);
       			resp = client.execute(hget);
       		} catch (ClientProtocolException cpe) {
        		handleGetImageError(false);
        		return;
        	} catch (IOException ioe) {
        		handleGetImageError(false);
        		return;
        	} catch (IllegalArgumentException iae) {
        		mNotFound.add(curArtist);
	        	handleGetImageError(true);
	        	return;
        	} catch (IllegalStateException ise) {
        		handleGetImageError(true);
        		return;
        	}
         	    	
        	if (resp == null) {
        		handleGetImageError(false);
       		}
        		
       		HttpEntity ent = resp.getEntity();
        	if (ent == null) {
        		handleGetImageError(true);
       			return;
       		}
        	while (mSettingBg) {
       			SystemClock.sleep(100);
       		}
        	
        	mSettingWikiImage = true;
        	Bitmap tempBmap = null;
        	System.gc();
       		try {
       			BitmapFactory.Options opt = new BitmapFactory.Options();
       			opt.inSampleSize = ssample;
       			tempBmap = BitmapFactory.decodeStream(new FlushedInputStream(ent.getContent()), 
       					null, opt);
       			if (tempBmap == null) {
       				//Log.e("Musician Slideshow", "Bitmap is null :(");
       				handleGetImageError(false);
       				return;
        		}
        	} catch (IOException ioe) {
       			handleGetImageError(false);
           		return;
       		} catch (OutOfMemoryError ome) {
       			Log.e("Musician Slideshow", "Error allocating memory for bitmap while loading musician: " + 
       					curArtist + " error:"+ ome.getMessage());
       			handleGetImageError(false);
       			return;
       		}
   
       		Background bg = scaleBmap(tempBmap, tempWikiImage);
       		if (bg == null) {
	     		handleGetImageError(false);
	        	return;
       		}
	       
       		String[] words = curArtist.split("[_]");
	        if (words == null)
	        	words = new String[] {curArtist};
	        
	      	tempWikiImage.setText(words);
	      	WikiCommonsImage newWikiImage = tempWikiImage;
	      
	      	if (mRenderer.getImageDisplay() != MsRenderer.CUBE_MODE && curWikiImage != null)
	      		mRenderer.setFadeOut(MsRenderer.MAX_FADEIN);
	      	else
	      		mRenderer.setFadeOut(MsRenderer.MAX_FADEOUT);
	      	
	    	if (curWikiImage == null) { 
	      		mRenderer.setCurWikiImage(newWikiImage);
	    	} else if (mRenderer.getImageDisplay() == MsRenderer.CUBE_MODE) {
	      		mRenderer.setCurWikiImage(newWikiImage);
	      		mRenderer.setNewWikiImage(null);
	    	} else {
	    		mRenderer.setNewWikiImage(newWikiImage);
	    	}

      		curWikiImage = newWikiImage;
	    	newWikiImage = null;
	        mSettingWikiImage = false;
        	if (mFirstImage) {
	       		mFirstImage = false;
        	}
        	
        	if (mRefreshGesture) {
        		mRefreshGesture = false;
    	       	mNotifyMan.cancelAll();
        	}
        
	       	mScheduledUpdate = System.currentTimeMillis() + mRefreshRate * 60 * 1000;	
	       	if (mVisible) {
	       		mImageViewed = true;
	       		mImageScheduler.setNextRun(mScheduledUpdate);
	       	} else
	        	mImageViewed = false;
	       	
	       	mRetries = 0;
	       	client.getConnectionManager().shutdown();
	       	client = null;
	       	mRenderer.setFadeIn(MsRenderer.MAX_FADEOUT);
     	    mGettingWikiImage = false;
       }
	   
    	private Background scaleBmap(Bitmap tempBmap, Background wikiImage) {
    	    float scalew;
   	        float scaleh;
   	        int canvasWidth = mRenderer.getCanvasWidth();
   	        int canvasHeight = mRenderer.getCanvasHeight();
   	        try {
   	        	scalew = (float) canvasWidth/tempBmap.getWidth();
   	        	scaleh = (float) canvasHeight/tempBmap.getHeight();
   	        } catch (ArithmeticException ae) {
   	        	scalew = 1.0f;
   	        	scaleh = 1.0f;
   	        }
   	        
   	        float scale = Math.min(scalew, scaleh);
   	        int maxTexSize = mRenderer.getMaxTexSize();
   	      	Bitmap scaledBmap = null;
   	      	int width, height;
   	        if (scale < 1.0) {
   	   			width = (int) (tempBmap.getWidth() * scale);
   	   			height = (int) (tempBmap.getHeight() * scale);
   	        } else {
   	   			width = tempBmap.getWidth();
   	   			height = tempBmap.getHeight();
   	        }

   	        wikiImage.setScaledWidth(width);
   	      	wikiImage.setScaledHeight(height);
   	      	double lgb2 = Math.log10(2);
            int heightExp = (int) Math.ceil(Math.log10(height)/lgb2);
            int widthExp = (int) Math.ceil(Math.log10(width)/lgb2);
            width = (int) Math.pow(2, widthExp);
   	        height = (int) Math.pow(2, heightExp);   
   	        if (width <= 0 || width > maxTexSize) {
   	          	width = maxTexSize;
   	        }
   	            
   	        if (height <= 0 || height > maxTexSize) {
   	           	height = maxTexSize;
   	        }
   	        
   	        wikiImage.setTexWidth(width);
   	      	wikiImage.setTexHeight(height);
   	        try {
   	        	System.gc();
   	        	scaledBmap = Bitmap.createScaledBitmap(tempBmap, width, height, true);		        	
   	        } catch (OutOfMemoryError ome) {
   	        	Log.e("Musician Slideshow", "Out of memory error while creating scaled bitmap" + ome.getMessage());
   	        	wikiImage = null;
   	        	tempBmap.recycle();
   	        	tempBmap = null;
   	        	return wikiImage;
   	        }
   	        
   	        if (tempBmap != scaledBmap) {
   	        	tempBmap.recycle();
   	        }
   	        
   	        tempBmap = null;
   	       	ByteBuffer buffer = null;
   	       	System.gc();
	     	try {
	     		buffer = ByteBuffer.allocateDirect(scaledBmap.getWidth() * scaledBmap.getHeight() * 4);
	     		buffer.order(ByteOrder.nativeOrder());
	     		byte[] newPixel = new byte[4];
	     		for (int y = scaledBmap.getHeight() - 1; y > 0; y--) {
	   				for (int x = 0; x < scaledBmap.getWidth(); x++) {
	   					int pixel = scaledBmap.getPixel(x, y);
    					byte red = (byte) (pixel >> 16 & 0xFF);
    					byte green = (byte) (pixel >> 8 & 0xFF);
	     				byte blue = (byte) (pixel & 0xFF);
	     				newPixel[0] = red;
	     				newPixel[1] = green;
	     				newPixel[2] = blue;
	     				newPixel[3] = (byte) 255;
	     				buffer.put(newPixel);
	     			}
	     		}
	     	
	     		scaledBmap.recycle();
	     		scaledBmap = null;
		     	buffer.position(0);
		      	wikiImage.setByteBuffer(buffer);
	     	} catch (OutOfMemoryError ome) {
	     		Log.e("Musician Slideshow", "Out of memory error while creating pixel buffer error: " + ome.getMessage());
	     		scaledBmap.recycle();
	     		scaledBmap = null;
	     		wikiImage = null;
	     		return wikiImage;
	     	}
	     	
	     	wikiImage.setScales(canvasWidth, canvasHeight);
			System.gc();
   	       	return wikiImage;
    	}
    	
    	private void handleGetImageError(boolean recoverable) {
    		mSettingWikiImage = false;
    		mGettingWikiImage = false;
    		if (client != null) {
    			client.getConnectionManager().shutdown();
    			client = null;
    		}
    		
    		if (mFirstImage || mRefreshGesture) {
    			if (mRetries < ERROR_MAX_RETRIES) {
    				mRetries++;
    				updateImageHandlerMills(200);
    			} else {
    				mRetries = 0;
    				if (mRenderer.getError() == MsRenderer.ERROR_NOERROR)
    					mRenderer.setError(MsRenderer.ERROR_MAXRETRIES);
    				
    				if (mRefreshGesture) {
    					mRefreshGesture = false;
    					mNotifyMan.cancelAll();
    				}
    				
    				updateImageHandlerMills(ERROR_STD);
    			}
    		} else if (recoverable) {
    			if (mRetries < ERROR_MAX_RETRIES) {
    				mRetries++;
    				updateImageHandlerMills(ERROR_RECOVER);
    			} else {
    				mRetries = 0;
    				if (mRenderer.getError() == MsRenderer.ERROR_NOERROR)
    					mRenderer.setError(MsRenderer.ERROR_GENERIC);
    				
    				if (mRefreshGesture) {
    					mRefreshGesture = false;
    		       		mNotifyMan.cancelAll();
    				}
    				
    				updateImageHandlerMills(ERROR_STD);
    			}
    		} else {
    			mRetries = 0;
    			if (mRenderer.getError() == MsRenderer.ERROR_NOERROR)
					mRenderer.setError(MsRenderer.ERROR_GENERIC);

    			updateImageHandlerMills(ERROR_STD);
    		}
    	
    		return;
    	}
    	
    	private void updateImageHandlerDiff(int mins, int oldRate) {
    		long timeLeft = mScheduledUpdate - System.currentTimeMillis();
    		long timeSpent = (mScheduledUpdate - timeLeft) - (mScheduledUpdate - oldRate * 60 * 1000);   		
    		if (timeLeft > 0) {
    			if (mins * 60 * 1000 < timeSpent) {
    				updateImageHandlerMills(200);
    			} else {
    				updateImageHandlerMills(mins * 60 * 1000 - timeSpent);
    			}
    		} else {
    			updateImageHandlerMins(mins);
    		}
    	}
    	
    	private void updateImageHandlerDiff() {
    		long diff = mScheduledUpdate - System.currentTimeMillis();
    		if (diff <= 0)
    			updateImageHandlerMills(200);
    		else
    			updateImageHandlerMills(diff);
    	}
    	
    	private void updateImageHandlerMins(int mins) {
    		long mills = mins * 60 * 1000;
    		updateImageHandlerMills(mills);
    	}
    	
    	private void updateImageHandlerMills(long mills) {
    		mScheduledUpdate = System.currentTimeMillis() + mills;
    		mImageScheduler.setNextRun(mScheduledUpdate);
    	}
    	
    	public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
    		boolean newB;
    		String newS;
    		int newI;
    		float newF;
    		
    		if (key == null || mRenderer == null) {
    			return;
    		}
    		
    		mSettingPreferences = true;
    		SharedPreferences settings = getSharedPreferences(SHARED_PREFS_NAME, 0);
    		SharedPreferences.Editor editor = settings.edit();
 	        
    		if (key.equals("ms_image_select")) {
    			newS = prefs.getString("ms_image_select", "best");
    			if (!newS.equals(mSelection)) {
    				mSelection = newS;
    				editor.putString("ms_image_select", mSelection); 	 
    			}    			
    		} else if (key.equals("ms_refresh_rate")) {
    			newS = prefs.getString("ms_refresh_rate", "3");
    			try {
    				newI = Integer.parseInt(newS) * 60;
    			} catch (NumberFormatException nfe) {
    				newI = 1 * 60;
    			}
    			
    			if (newI != mRefreshRate) {
    				int oldRate = mRefreshRate;
    				mRefreshRate = newI;
    				if (!isPreview())
    					updateImageHandlerDiff(mRefreshRate, oldRate);
    				
    	            editor.putString("ms_refresh_rate", newS);
    			}
    			
    		} else if (key.equals("ms_display_artist")) {
    			newB = prefs.getBoolean("ms_display_artist", true);
    			if (newB != mRenderer.getDisplayArtistImage()) {
    				mRenderer.setDisplayArtistImage(newB);
    				editor.putBoolean("ms_display_artist", newB);
    			}
    		} else if (key.equals("ms_display_artist_visible")) {
    			newB = prefs.getBoolean("ms_display_artist_visible", false);
    			if (newB != mRenderer.getDisplayArtistVisible()) {
    				mRenderer.setDisplayArtistVisible(newB);
    				editor.putBoolean("ms_display_artist_visible", newB);
    			}
    		} else if (key.equals("ms_image_full")) {
    			newB = prefs.getBoolean("ms_keep_artist", false);
    			if (newB != mFull) {
    			    mFull = newB;
    				editor.putBoolean("ms_image_full", mFull);
    			}
    		} else if (key.equals("ms_text_color")) {
    			newS = prefs.getString("ms_text_color", "-1");
    			try {
    				newI = Integer.parseInt(newS);
    			} catch (NumberFormatException nfe) {
    				newI = -1;
    			}
    			
    			if (newI != mRenderer.getTextColor()) {
    				mRenderer.setTextColor(newI);
    				editor.putString("ms_text_color", newS);
    			}
    		} else if (key.equals("ms_artist_text_size")) {
    			newS = prefs.getString("ms_artist_text_size", "40.0");
    			try {
    				newF = Float.parseFloat(newS);
    				if (newF < 20 || newF > 100)
    					newF = 40.0f;
    			} catch (NumberFormatException nfe) {
    				newF = 40.0f;
    			}
    			
    			if (newF != mRenderer.getTextSize()) {
    				mRenderer.setTextSize(newF);
    				editor.putString("ms_artist_text_size", newS);
    			}
    		} else if (key.equals("ms_artist_skip")) {
    			  boolean skip = prefs.getBoolean("ms_artist_skip", true);
    			  if (skip != mSkipArtist) {
    				  mSkipArtist = skip;
    				  editor.putBoolean("ms_artist_skip", mSkipArtist);
    			  }
    		} else if (key.equals("ms_image_display")) {
    			newS = prefs.getString("ms_image_display", "cube");
    			int imageDisplay = mRenderer.getImageDisplay();
    			if ((!newS.equals("cube") && imageDisplay == MsRenderer.CUBE_MODE) || (!newS.equals("wall") && 
    					imageDisplay == MsRenderer.WALL_MODE) || 
    					(!newS.equals("zoom") && imageDisplay == MsRenderer.ZOOM_MODE)) { 
    				if (newS.equals("cube") && !mCubeBg.equals("none") && !isPreview()) {
    					mRenderer.settingBackground(true);
    					mRenderer.setRelease(MsRenderer.RELEASE_WALL_TEX);
    					setBackground(false);
    					mRenderer.setNewBg();
						mRenderer.settingBackground(false);
    				} else{
    					mBackground.setByteBuffer(null);
    					mRenderer.setRelease(MsRenderer.RELEASE_CUBE_TEX | MsRenderer.RELEASE_WALL_TEX);
    				}
    				
    				setImageDisplay(newS);
    				editor.putString("ms_image_display", newS);
    			}
    		} else if (key.equals("ms_cube_bg")) {
    			newS = prefs.getString("ms_cube_bg", "heic0719a");
    			if (!newS.equals(mCubeBg) && !newS.equals("custom_background")) {
    				mCubeBg = newS;
    				editor.putString("ms_cube_bg", mCubeBg);
    				if (!mCubeBg.equals("none") && 
    						mRenderer.getImageDisplay() == MsRenderer.CUBE_MODE && !isPreview()) {
    					mRenderer.settingBackground(true);
    					mRenderer.setRelease(MsRenderer.RELEASE_WALL_TEX);
						setBackground(false);
				     	mRenderer.setNewBg();
						mRenderer.settingBackground(false);
    				} else if (mCubeBg.equals("none") && !isPreview() && 
    						mRenderer.getImageDisplay() == MsRenderer.CUBE_MODE) {
    					mBackground.setByteBuffer(null);
    					mRenderer.setRelease(MsRenderer.RELEASE_WALL_TEX);
    					mRenderer.setClearColor();
    				}
    			}
    	   } else if (key.equals("ms_cubebg_stretch")) {
    		   newB = prefs.getBoolean("ms_cubebg_stretch", true);
     		   if (newB != mRenderer.getStretchBg()) {
     			   editor.putBoolean("ms_cubebg_stretch", newB);
     			   mRenderer.setStretchBg(newB);
     		   }
     	   } else if (key.equals("ms_wall_stretch")) {
    		   newB = prefs.getBoolean("ms_wall_stretch", true);
     		   if (newB != mRenderer.getStretchWall()) {
     			   editor.putBoolean("ms_wall_stretch", newB);
     			   mRenderer.setStretchWall(newB);
     		   }
     	   } else if (key.equals("ms_custom_background_uri")) {
     		   newS = prefs.getString("ms_custom_background_uri", "unknown");
    		   if (!mCubeBg.equals("custom_background") || !newS.equals(mCubeCustomBg)) {
    			   mCubeCustomBg = newS;
    			   editor.putString("ms_cube_bg", "custom_background");
    			   mCubeBg = "custom_background";
    			   if (mRenderer.getImageDisplay() == MsRenderer.CUBE_MODE && !isPreview()) {
    				   mRenderer.settingBackground(true);
    				   mRenderer.setRelease(MsRenderer.RELEASE_WALL_TEX);
    				   setBackground(true);
    				   mRenderer.setNewBg();
    				   mRenderer.settingBackground(false);
    			   }
    		   }
    	   } else if (key.equals("ms_cube_color")) {
    		   newS = prefs.getString("ms_cube_color", "0");
    		   int color;
    		   try {
    			   color = Integer.parseInt(newS);
               } catch (NumberFormatException nfe) {
            	   color = 0;
               }
               
    		   if (color != mRenderer.getClearColor()) {
    			   editor.putString("ms_cube_color", newS);
				   mRenderer.setClearColor(color);
    			   mRenderer.setClearColor();
    		   }
    	   } else if (key.equals("ms_cube_scale")) {
			  boolean scale = prefs.getBoolean("ms_cube_scale", true);
			  if (scale != mRenderer.getCubeScale()) {
				  mRenderer.setCubeScale(scale);
				  editor.putBoolean("ms_cube_scale", scale);
			  }
		  } 
    		
    	   editor.commit();
    	   mSettingPreferences = false;
        } 
    	
    	@Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);
            setTouchEventsEnabled(true);
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            mDestroyed = true;
            while (mSettingBg || mGettingWikiImage || mSettingPreferences) {
            	SystemClock.sleep(100);
            }
            
            mRenderer.release();
            mRenderer = null;
            
            if (mPrefs != null)
        		mPrefs.unregisterOnSharedPreferenceChangeListener(this);
        	
        	curWikiImage = null;
            mNotFound = null;
            mBackground = null;
            mWall = null;
            mCube = null;
            mNotifyMan = null;
            //setTouchEventsEnabled(false);
            mImageScheduler.destroy();
            mImageScheduler = null;
            System.gc();
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            mVisible = visible;
      		if (!mVisible) {
      			mRenderer.setSleep(true);
      			if (!mImageViewed && !mFirstImage) 
      				mImageScheduler.cancelNextRun();
      		} else {
      			mRenderer.animateText();
   				mRenderer.setSleep(false);
   				if (!mImageViewed && !isPreview() && !mFirstImage) {
   					mImageViewed = true;
   					mScheduledUpdate = System.currentTimeMillis() + mRefreshRate * 60 * 1000;
   					mImageScheduler.setNextRun(mScheduledUpdate);
   				}
   			}      		
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
        }
        
        @Override
        public void onOffsetsChanged(float xOffset, float yOffset,
                float xStep, float yStep, int xPixels, int yPixels) {
        }
        
        @Override
        public void onTouchEvent(MotionEvent event) {
        	super.onTouchEvent(event);
        	if (mRenderer == null)
        		return;
        	
            if (event.getAction() == MotionEvent.ACTION_MOVE) {
               long curTime = System.currentTimeMillis();
               if (curTime - mLastTouch > 500) {
            	   mNumTaps = 0;
            	   mLastTouch = 0;
               }
               
               mLastTouch = curTime;
               mNumTaps++;
               int canvasWidth = mRenderer.getCanvasWidth();
               int canvasHeight = mRenderer.getCanvasHeight();
               float midY = canvasHeight/2;
               float midX = canvasWidth/2;
               float curX = event.getX();
        	   float curY = event.getY();
        	   if (mNumTaps == 1) {
            	   mFirstTouchX = event.getX();
            	   mFirstTouchY = event.getY();
               }
        	   
        	   float xThres = (float) ((float) canvasWidth * .7);
        	   float yThres = (float) ((float) canvasHeight * .7);

               if (mNumTaps > 1 && mFirstTouchX < midX && mFirstTouchY > midY && curX > midX && 
            		   curY < midY && Math.abs(mFirstTouchX - curX) > xThres && Math.abs(mFirstTouchY - curY) > yThres
            		   && curWikiImage != null && curWikiImage.getInfoLink() != null) { 
            	   mNumTaps = 0;
            	   mLastTouch = 0;
            	   String link = null;
            	   if (curWikiImage != null) {
            		   link = curWikiImage.getInfoLink();
            	   }
            	   
            	   if (link != null) {
            		   Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
            		   intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            		   startActivity(intent); 
            	   }
               } else if (mNumTaps > 1 && mFirstTouchX > midX && mFirstTouchY < midY && curX < midX && 
            		   curY > midY && Math.abs(mFirstTouchX - curX) > xThres && Math.abs(mFirstTouchY - curY) > yThres 
            		   && !isPreview()) {
            	   mNumTaps = 0;
            	   mLastTouch = 0;
            	   int icon = android.R.drawable.sym_def_app_icon;
            	   CharSequence tickText = "Musician Slideshow: Loading Image";
            	   Notification notification = new Notification(icon, tickText, System.currentTimeMillis());
            	   Context context = getApplicationContext();
            	   CharSequence cTitle = "Musician Slideshow";
            	   CharSequence cText = "Trying to download an image";
            	   Intent notifyIntent = new Intent();
            	   PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notifyIntent, 0);
            	   notification.setLatestEventInfo(context, cTitle, cText, contentIntent);
            	   mNotifyMan.notify(GET_IMAGE, notification);
            	   mRefreshGesture = true;
            	   updateImageHandlerMills(500);
               } else if (mNumTaps > 1 && mFirstTouchX > midX && mFirstTouchY > midY && curX < midX && 
            		   curY < midY && Math.abs(mFirstTouchX - curX) > xThres && Math.abs(mFirstTouchY - curY) > yThres) {
            	   mRenderer.animateText();
            	   mNumTaps = 0;
            	   mLastTouch = 0;
               }
            }
            
            //event.recycle();
       }
    }
}